/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			ResManager.d
	Description:	resource manager - loads, creates, stores and disposes all resource objects
	Date:			25.11.2007 by Digited
*/
module moonglide.resources.Management;

public import		// this module public import all basic resources, so just import this module to use them
	moonglide.math.MoonMath,
	moonglide.util.MoonUtil,
	moonglide.io.LogSystem,
	moonglide.io.MoonFile,

	moonglide.core.Events,

	moonglide.resources.resobjects.lowlevel.ResMesh,
	moonglide.resources.resobjects.lowlevel.ResShader,
	moonglide.resources.resobjects.lowlevel.ResTextureData,
	moonglide.resources.resobjects.hilevel.ResTexture,
	moonglide.resources.resobjects.hilevel.ResMaterial,
	moonglide.resources.resobjects.hilevel.ResShaderComposite,

	moonglide.resources.resproc.mesh.ResPMesh;

import
	tango.io.Path,
	moonglide.resources.resproc.texture.ResPTex,

	moonglide.resources.io.IResIOProvider;

alias tango.io.Path.parse pathParse;

enum EResLoadState
{
	notloaded,		// resource is not loaded to render system, and to upload manager
	loading,		// resource is loaded to upload manager, but not to render system
	loaded			// resource is loaded to render system, and to upload manager
}

//================================================================
/// abstract basic class of all resource objects
//================================================================
abstract class IResource : IEventGenerator
{
	mixin TEventGenerator;

	protected:
		bool			_dead;
		EResLoadState	_loadedS;

		int	_sizeRes = 32,	// how many times we can try to load this resource
			_refCount = 1;	// if created than used once for sure!

		void	_SetID( char[] newID ) { _id = newID; } // dangerous thing, use only in ResManager
														// for generating unique resource names
		// protected constructor - every descendent must have it abstract too
		this( char[] id )
		in
		{
			moonAssert( id.length > 0, "id is empty!", __FILE__, __LINE__ );
		}
		body
		{
			_id = id;
		}

		//----------- access to resource manager protected staff ----------------------
		static void _New( IResource res )
		{
			CResManager()._New( res );
		}

		static void _Load( IResource res, char[] errorResName )
		{
			CResManager()._Load( res, errorResName );
		}
		//-----------------------------------------------------------------------------

	public:
		int getSize() { return _sizeRes; }
		bool isAlive() { return !_dead; }

		//============================================================
		/// destructor - do NOT use it directly, instead use Release()
		//============================================================
		~this()
		{
			_dead = true;

			debug debugLog.trace( "Destroying resource: " ~ _id );
			CResManager()._onResourceDestruction( this );
		}

		//============================================================
		/// save this resource to file with its io manager
		//============================================================
		bool save( char[] filename )
		{
			return CResManager()._Save( this, filename );
		}

		//============================================================
		/// RefCount returns number of references to this resource
		//============================================================
		uint refCount() { return _refCount; }

		//============================================================
		/// Retain - increases reference counter.
		/// For ease of use, returns self: IResource reference = existingResource.Retain;
		//============================================================
		IResource retain() { _refCount++; return this; }

		//============================================================
		/// Release - decreases reference counter. If refCount == 0, resource is destroyed.
		/// Use this instead of destructor
		//============================================================
		void release( uint count = 1 )
		{
			_refCount -= count;

			if( _refCount <= 0 )
				delete this;
		}

		//================================================================
		/// NOTE: do NOT use this until you don't know what for - added for resource manager only
		//================================================================
		void loadState( EResLoadState load ) { _loadedS = load; }
		EResLoadState LoadState() { return _loadedS; }

		void setNotLoaded() { _loadedS = EResLoadState.notloaded; }
		void setLoading() { _loadedS = EResLoadState.loading; }
		void setLoaded() { _loadedS = EResLoadState.loaded; }
		bool loaded() { return _loadedS == EResLoadState.loaded; }

		void loadToDevice() { CResManager()._onResourceLoadToDevice( this ); }
		void unLoadFromDevice() { CResManager()._onEventUnloadFromDevice( this ); }
}


//================================================================
/// main class that manages resources
//================================================================
class CResManager : IEventListener, IEventGenerator
{
	mixin TEventGenerator;

	protected:
		IResource[ char[] ]		 _resources;
		IResIOProvider[ char[] ] _providers;

		CEvent _event;

		this()  // singleton!
		{
			_id = "ResManager";
			_event = new CEvent;
		}

		void _onResourceDestruction( IResource res )
		{
			_event.type = eeventtype.resource;
			_event.subtype = eresourceevent.destroyed;

			passEvent( _event, res );
			 _resources.remove( res.id );
		}

		void _onResourceLoadToDevice( IResource res )
		{
			_event.type = eeventtype.resource;
			_event.subtype = eresourceevent.load;
			passEvent( _event, res );
		}

		void _onEventUnloadFromDevice( IResource res )
		{
			_event.type = eeventtype.resource;
			_event.subtype = eresourceevent.unload;
			passEvent( _event, res );
		}

		//===============================================================
		/// crash defence: generate data for resource, if it wasn't loaded somehow
		//===============================================================
		void _GenerateErrorData( IResource res, char[] errorResName )
		{
			if( res.id in _providers )
			{
				_providers[ res.id ].CreateErrorResourceData( res, errorResName );

				_event.type = eeventtype.resource;
				_event.subtype = eresourceevent.created;
				_event.objects["res"] = cast(Object)res;
				generateEvent( _event );
			}
			else
				errorLog( "resmanager: no provider for " ~ res.id ~ ", can't generate error data" );
		}

		//================================================================
		/// adds new res to array
		//================================================================
		void _New( IResource res )
		{
			debug if( res.id in _resources )
			{
				errorLog( "new resource [" ~ res.id ~ "] is already in _resource array!" );
				return;
			}

			_resources[ res.id ] = res;

			_event.type = eeventtype.resource;
			_event.subtype = eresourceevent.created;
			_event.objects["res"] = cast(Object)res;
			generateEvent( _event );
		}

		//================================================================
		/// try to load file with res.id, then try to load resource from file.
		//================================================================
		void _Load( IResource res, char[] errorResName )
		in
		{
			moonAssert( res.id.length > 0, "res id is empty!", __FILE__, __LINE__ );
		}
		body
		{
//---------------------------------------------------------------------
				void _Failure( char[] errorMsg )
				{
					errorLog( errorMsg );
					traceLog.trace( res.id ~ " not loaded. Creating error data for that resource..." );
					_GenerateErrorData( res, errorResName );
				}
//---------------------------------------------------------------------

			static VfsFile file;

			try
			{
				file = CMoonFile().file( res.id );
			}
			catch( Exception e )
			{
				_Failure( "ResManager: error checking file existance: " ~ res.id ~ ", " ~
					e.msg );
				return;
			}

			if( !file || !file.exists )
			{
				_Failure( "ResManager: file doesn't exist: " ~ res.id );
				return;
			}

			static InputStream filestream = null;

			try
			{
				filestream = file.input;
			}
			catch( Exception e )
			{
				_Failure( "ResManager: error loading file " ~ res.id ~ ", " ~ e.msg );
				return;
			}

			if( filestream )
			{
				traceLog.info( "Resmanager: file loaded: " ~ res.id );

				if( res.classinfo.name in _providers )
				{
					try
					{
						_providers[ res.classinfo.name ].Load( pathParse( res.id ).ext, res, filestream, file.size );
					}
					catch( Exception e )
					{
						_Failure( "ResManager: error loading file with provider: " ~ res.id ~ ", " ~ e.msg );
						return;
					}
					finally
					{
				/* multithread	*/	filestream.close;
					}
				}
				else
				{
					filestream.close;
					_Failure( "ResManager: no registered provider for " ~ res.id );
					return;
				}
			}
			else
			{
				_Failure( "ResManager: could not load " ~ res.id );
				return;
			}

			traceLog.info( "ResManager: resource successfully loaded: " ~ res.id );
			_resources[ res.id ] = res;

			_event.type = eeventtype.resource;
			_event.subtype = eresourceevent.created;
			_event.objects["res"] = cast(Object)res;
			generateEvent( _event );
		}

		//================================================================
		/// save file using registered provider for given type
		//================================================================
		bool _Save( IResource res, char[] filename )
		in
		{
			moonAssert( res !is null, "res is null", __FILE__, __LINE__ );
			moonAssert( filename.length > 0, "filename is empty", __FILE__, __LINE__ );
		}
		body
		{
			Array buf = new Array( 262144, 262144 ); // create a buffer for writing resource in binary

			if( res.classinfo.name in _providers )
				if( _providers[ res.classinfo.name ].Save( pathParse( filename ).ext, res, buf ) )
				{
					try
					{
						VfsFolder progFolder;

						if( "program" in CMoonFile.drivers )
							progFolder = CMoonFile.drivers[ "program" ];
						else
							throw new Exception( "no program driver in CMoonFile.drivers!" );

						VfsFile savingFile = progFolder.file( filename );

						if( savingFile.exists )
							traceLog.trace( "[ ResManager ] Overwriting file " ~ filename );
						else
						{
							savingFile.create;
							traceLog.trace( "[ ResManager ] Creating file " ~ filename );
						}

						OutputStream sfs = savingFile.output;
						sfs.copy( buf );
						sfs.close;
					}
					catch( Exception e )
					{
						errorLog( "error writing resource to file " ~ filename ~ ", reason: " ~ e.msg );
						return false;
					}
					finally
					{
						buf.close;
					}

					return true;
				}
				else
				{
					errorLog( "ResManager: provider failed to save " ~ filename );
					return false;
				}
			else
			{
				errorLog( "ResManager: no registered provider for " ~ filename );
				return false;
			}
		}

		static CResManager _singleton;

	public:
		static CResManager opCall()
		{
			if( _singleton is null )
				_singleton = new CResManager;

			return _singleton;
		}

		bool GenerateUniqueName( ref char[] name )
		{
		    bool test(char[] name)
		    {
		        return !Exists( name );
		    }

		    name = uniqName( name, &test );
		    return true;
		}


		void onEventCallback( CEvent event, IEventGenerator gen ) {}
		void onEventReceive( CEvent event, IEventSender sender ) {}

		//================================================================
		/// Get an existing resource with increasing its ref counter
		//================================================================
		IResource GetResource( char[] name )
		{
			if( Exists( name ))
				return _resources[ name ].retain;

			return null;
		}

		//================================================================
		/// check if resource exists/loaded
		//================================================================
		bool Exists( char[] name )
		{
			return ( name in _resources ) != null;
		}

		//================================================================
		/// subj. Returns false if there were no resources
		//================================================================
		bool destroyAllResources()
		{
			debug DbgReport;

			if( _resources.length > 0 )
			{
				debug debugLog.trace( "resmanager is destroing ALL resources!" );

                bool found;

				// materials die first
                while(1)
                {
                    found = false;

                    foreach( inout res; _resources )
                        if( res && res.isAlive )
                        	if( cast( CResMaterial )res )
							{
								found = true;
								debug debugLog.trace( res.id ~ " is being destroyed..." );

								if( res.loaded )
								{
									debug debugLog.trace( res.id ~ " is being unloaded..." );
									res.unLoadFromDevice();
								}

								delete res;
								break;
							}

                    if( !found )
                        break;
                }

				// then Texture compositors die!
                while(1)
                {
                    found = false;

                    foreach( inout res; _resources )
                        if( res && res.isAlive )
                        	if( cast( CResTexture )res )
							{
								found = true;
								debug debugLog.trace( res.id ~ " is being destroyed..." );

								if( res.loaded )
								{
									debug debugLog.trace( res.id ~ " is being unloaded..." );
									res.unLoadFromDevice();
								}

								delete res;
								break;
							}

                    if( !found )
                        break;
                }

                while( _resources.length > 0 )
				{
					IResource res = _resources.values[0];

					if( res )
						if( res.isAlive )
						{
							debug debugLog.trace( res.id ~ " is being destroyed..." );

							if( res.loaded )
							{
								debug debugLog.trace( res.id ~ " is being unloaded..." );
								res.unLoadFromDevice();
							}

							delete res;
						}
						else
							_resources.remove( res.id );	// if somehow it didn't delete itself from res array
				}

				return true;
			}
			else
				return false;

			debug DbgReport;
		}

//---------- end of resource staff -----------------------------------------------------

		//================================================================
		/// add new loader
		//================================================================
		void RegisterProvider( IResIOProvider provider )
		{
			if(( provider.GetResourceClass() in _providers ) == null )
			{
				traceLog.info( "resmanager: new provider registered for class " ~
				provider.GetResourceClass() );
				_providers[ provider.GetResourceClass() ] = provider;
			}
			else
				errorLog( "resmanager: already have a loader for class " ~ provider.GetResourceClass() );
		}

		//================================================================
		/// destructor stuff
		//================================================================
		~this()
		{
			debug debugLog.trace( "resmanager destructor started..." );
			// remove and unload all resources
			destroyAllResources();

			foreach( ref prov; _providers )
				delete prov;

			_providers = null;
		}

		debug void DbgReport()
		{
			debugLogY( "----------------" );
			debugLogY( "ResManager. begin of report" );
			debugLogY( "File: <" ~ __FILE__ ~ ">" );
			debugLogY( "----------------" );


			foreach( inout Res; _resources)
				debugLog.trace( Format( "[{}] use count ==  {}", Res.id, Res.refCount ));

			debugLogY("ResManager. end of report");
			debugLogY("----------------");
			consolePause;
		}
}
